/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { RuleListUtil } from "../../utils/common/DefectsList";
import { ArkFile, ts } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects } from "../../model/Defects";
import { FileMatcher, MatcherCallback, MatcherTypes } from "../../matcher/Matchers";
import { AstTreeUtils } from "arkanalyzer";
import { Rule } from "../../model/Rule";
import { IssueReport } from '../../model/Defects';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoMagicNumbersCheck');
const gMetaData: BaseMetaData = {
    severity: 1,
    ruleDocPath: "docs/no-magic-numbers-check.md",
    description: "Magic number found: ${magicNumber}. Consider using a named constant"
};

interface MagicNumberCheckResult {
    line: number;
    character: number;
    message: string;
}

export type Option = {
    ignoreEnums?: boolean;
    ignoreNumericLiteralTypes?: boolean;
    ignoreReadonlyClassProperties?: boolean;
    ignoreTypeIndexes?: boolean;
}

export class NoMagicNumbersCheck implements BaseChecker {
    private defaultOption: Option = { ignoreEnums: false, ignoreNumericLiteralTypes: false, ignoreReadonlyClassProperties: false, ignoreTypeIndexes: false };
    private option: Option = this.defaultOption;
    readonly metaData: BaseMetaData = gMetaData;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    public rule: Rule;
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    public registerMatchers(): MatcherCallback[] {
        const matchfileBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        };
        return [matchfileBuildCb];
    }
    public check = (targetField: ArkFile) => {
        if (this.rule && this.rule.option && this.rule.option[0]) {
            const ruleOption = this.rule.option[0] as Option;
            this.option.ignoreEnums = ruleOption.ignoreEnums ?? this.defaultOption.ignoreEnums;
            this.option.ignoreNumericLiteralTypes = ruleOption.ignoreNumericLiteralTypes ?? this.defaultOption.ignoreNumericLiteralTypes;
            this.option.ignoreReadonlyClassProperties = ruleOption.ignoreReadonlyClassProperties ?? this.defaultOption.ignoreReadonlyClassProperties;
            this.option.ignoreTypeIndexes = ruleOption.ignoreTypeIndexes ?? this.defaultOption.ignoreTypeIndexes;
        }
        const sourceFile = AstTreeUtils.getASTNode(targetField.getName(), targetField.getCode());
        const results = this.checkMagicNumbers(sourceFile);
        results.forEach(result => {
            this.addIssueReport(result.line, result.character, targetField.getFilePath(), result.message);
        });
    };
    private checkMagicNumbers(sourceFile: ts.SourceFile): MagicNumberCheckResult[] {
        const results: MagicNumberCheckResult[] = [];
        function isMagicNumber(node: ts.Node): node is ts.LiteralExpression {
            if (node.parent && ts.isVariableDeclaration(node.parent)) {
                return false;
            }
            if (ts.isPrefixUnaryExpression(node)) {
                // 如果是一元运算符表达式（如 -2），并且操作符是 `-`
                if ((node.operator === ts.SyntaxKind.PlusToken
                    || node.operator === ts.SyntaxKind.MinusToken) && ts.isNumericLiteral(node.operand)) {
                    return true;
                }
            }
            return ts.isNumericLiteral(node) || ts.isBigIntLiteral(node);
        }
        function isIgnoredNumericLiteralType(node: ts.Node): boolean {
            // 检查是否是字面量类型或联合类型
            if (ts.isTypeNode(node)) {
                if (isTypeIndexAccess(node, true)) {
                    return false;
                }
                if (ts.isLiteralTypeNode(node)) {
                    if (ts.isNumericLiteral(node.literal) || ts.isBigIntLiteral(node.literal)) {
                        return true;
                    }
                    // 特殊处理：检查是否是一元运算符表达式（如 -2）
                    if (ts.isPrefixUnaryExpression(node.literal)) {
                        // 检查操作符是否为 -，并且操作数是否是正数字字面量
                        return (
                            (node.literal.operator === ts.SyntaxKind.PlusToken
                                || node.literal.operator === ts.SyntaxKind.MinusToken)
                            && ts.isNumericLiteral(node.literal.operand)
                        );
                    }
                }

                if (ts.isUnionTypeNode(node)) {
                    // 联合类型中的每个成员
                    return node.types.every((type) => {
                        return ts.isLiteralTypeNode(type) &&
                            isSingleNumericLiteralType(type);
                    });
                }
            }
            return false;
        }

        function isSingleNumericLiteralType(node: ts.Node): boolean {
            if (ts.isLiteralTypeNode(node)) {
                if (ts.isNumericLiteral(node.literal) || ts.isBigIntLiteral(node.literal)) {
                    return true;
                }
                // 特殊处理：检查是否是一元运算符表达式（如 -2）
                if (ts.isPrefixUnaryExpression(node.literal)) {
                    // 检查操作符是否为 -，并且操作数是否是正数字字面量
                    return (
                        (node.literal.operator === ts.SyntaxKind.PlusToken
                            || node.literal.operator === ts.SyntaxKind.MinusToken)
                        && ts.isNumericLiteral(node.literal.operand)
                    );
                }
            }
            return false;
        }

        function isIgnoredEnumMember(node: ts.Node): boolean {

            if (ts.isEnumMember(node.parent)) {
                // 如果初始化器是数字字面量
                if (ts.isNumericLiteral(node)) {
                    return true;
                }
                // 如果初始化器是一元运算符表达式（如 -8 或 +9）
                if (ts.isPrefixUnaryExpression(node)) {
                    // 检查操作符是否为 - 或 +，并且操作数是否是数字字面量
                    return (
                        (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) &&
                        ts.isNumericLiteral(node.operand)
                    );
                }
            }
            return false;
        }
        function isTypeIndexAccess(node: ts.Node, interfaces: boolean): boolean {
            // 检测索引访问类型节点：Type[Index]
            if (ts.isTypeNode(node)) {
                const typeExpression = getAncestorNodes(node);
                for (const ancestor of typeExpression) {
                    if (ts.SyntaxKind[ancestor.kind] === 'IndexedAccessType') {
                        return true;
                    }
                    else if (interfaces && ts.SyntaxKind[ancestor.kind] === 'InterfaceDeclaration') {
                        return true;
                    }
                }
            }
            return false;
        }

        function getAncestorNodes(node: ts.Node): ts.Node[] {
            const ancestors: ts.Node[] = [];
            let currentNode: ts.Node | undefined = node;
            while (currentNode) {
                ancestors.push(currentNode);
                currentNode = currentNode.parent;
            }
            return ancestors;

        }
        function isIgnoredClassProperty(node: ts.Node): boolean {
            if (ts.isPropertyDeclaration(node)) {
                if (node.modifiers) {
                    const isReadonly = node.modifiers.some(mod => mod.kind === ts.SyntaxKind.ReadonlyKeyword);
                    const isStatic = node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
                    if (isReadonly || isStatic) {
                        return true;
                    }
                }
            }
            return false;
        }
        const visitNode = (node: ts.Node): void => {
            if (isMagicNumber(node)) {
                const isMenu = this.option.ignoreEnums && isIgnoredEnumMember(node);
                const isignoreNumerType = this.option.ignoreNumericLiteralTypes && isIgnoredNumericLiteralType(node.parent);
                const isTypeIndex = this.option.ignoreTypeIndexes && isTypeIndexAccess(node.parent, false);
                const isReadonlyClass = this.option.ignoreReadonlyClassProperties && isIgnoredClassProperty(node.parent);
                if (isignoreNumerType || isMenu || isReadonlyClass || isTypeIndex) {
                    return;
                }
                const magicNumber = node.getText(sourceFile);
                const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile, true));
                results.push({
                    line: line + 1,
                    character: character + 1,
                    message: `Magic number found: ${magicNumber}. Consider using a named constant.`
                });

            }
            if (!ts.isPrefixUnaryExpression(node)) {
                ts.forEachChild(node, visitNode);
            }
        };

        visitNode(sourceFile);
        return results;
    }

    private async addIssueReport(line: number, startCol: number, filePath: string, message: string) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const fixKey = `${line}%${startCol}%${this.rule.ruleId}`;
        const mergeKey = `${filePath}%${fixKey}%${message}`;
        const description = message;
        const defect = new Defects(line, startCol, description, severity,
            this.rule.ruleId, mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}